package com.metroinfo.servers.knjiga;

import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.Selector;
import java.nio.channels.SelectionKey;
import java.nio.channels.SelectableChannel;
import java.net.ServerSocket;
import java.net.InetSocketAddress;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.metroinfo.infrastructure.MainDataStructure;
import com.metroinfo.workers.ChannelIO;
import com.metroinfo.workers.RequestServicer;
import com.metroinfo.workers.RequestServicerBuffer;

/**
 * Simple echo-back server which listens for incoming stream connections and
 * echoes back whatever it reads. A single Selector object is used to listen to
 * the server socket (to accept new connections) and all the active socket
 * channels.
 * 
 * @author Ron Hitchens (ron@ronsoft.com) 
 */
public class SelectSockets {
	public static int PORT_NUMBER = 8000;
	private static final int POOL_MULTIPLE = 4;
	private int brojac = 0;
	private long vreme = 0;
	ByteBuffer bb = ByteBuffer.allocate(50);

	public static void main(String[] argv) throws Exception {
		SelectSockets ss = new SelectSockets();
		ss.go(argv);
	}
	
	public SelectSockets(){
		mainDataStructure = new MainDataStructure();
	}
	
	ExecutorService xec = Executors.newFixedThreadPool(Runtime.getRuntime()
			.availableProcessors()
			* POOL_MULTIPLE);

	private final MainDataStructure mainDataStructure;

	public void go(String[] argv) throws Exception {
		int port = PORT_NUMBER;
		
		System.out.println("Listening on port " + port);
		// Allocate an unbound server socket channel
		ServerSocketChannel serverChannel = ServerSocketChannel.open();
		// Get the associated ServerSocket to bind it with
		ServerSocket serverSocket = serverChannel.socket();
		// Create a new Selector for use below
		Selector selector = Selector.open();
		// Set the port the server channel will listen to
		serverSocket.bind(new InetSocketAddress(port));
		// Set nonblocking mode for the listening socket
		serverChannel.configureBlocking(false);
		// Register the ServerSocketChannel with the Selector
		serverChannel.register(selector, SelectionKey.OP_ACCEPT);
		while (true) {
			// This may block for a long time. Upon returning, the
			// selected set contains keys of the ready channels.
			int n = selector.select();
			if (n == 0) {
				continue; // nothing to do
			}
			// Get an iterator over the set of selected keys
			Iterator<SelectionKey> it = selector.selectedKeys().iterator();
			// Look at each key in the selected set
			while (it.hasNext()) {
				//SelectionKey key = (SelectionKey) it.next();
				SelectionKey key = it.next();
				// Is a new connection coming in?
				if (key.isAcceptable()) {
					ServerSocketChannel server = (ServerSocketChannel) key
							.channel();
					
					brojac++;
					if (brojac == 1) vreme = System.currentTimeMillis();
					
					if (brojac == 1000){
						brojac = 0;
						System.out.println("Vreme="+(System.currentTimeMillis()-vreme));
					}

					
					SocketChannel channel = server.accept();
					registerChannel(selector, channel, SelectionKey.OP_READ);
					//sayHello(channel);
				}
				// Is there data to read on this channel?
				if (key.isReadable()) {
					readDataFromSocket(key);
				}
				// Remove key from selected set; it's been handled
				it.remove();
			}
		}
	}

	// ----------------------------------------------------------
	/**
	 * Register the given channel with the given selector for the given
	 * operations of interest
	 */
	protected void registerChannel(Selector selector,
			SelectableChannel channel, int ops) throws Exception {
		if (channel == null) {
			return; // could happen
		}
		// Set the new channel nonblocking
		channel.configureBlocking(false);
		// Register it with the selector
		channel.register(selector, ops);
	}

	// ----------------------------------------------------------
	// Use the same byte buffer for all channels. A single thread is
	// servicing all the channels, so no danger of concurrent acccess.
	private ByteBuffer buffer = ByteBuffer.allocateDirect(20);

	/**
	 * Sample data handler method for a channel with data ready to read.
	 * 
	 * @param key
	 *            A SelectionKey object associated with a channel determined by
	 *            the selector to be ready for reading. If the channel returns
	 *            an EOF condition, it is closed here, which automatically
	 *            invalidates the associated key. The selector will then
	 *            de-register the channel on the next select call.
	 */
	protected void readDataFromSocket(SelectionKey key) throws Exception {
		SocketChannel socketChannel = (SocketChannel) key.channel();
		int count;

		/*
		buffer.clear();
		while ((count = socketChannel.read (buffer)) > 0) {
			buffer.flip(); // Make buffer readable
			}
		*/
/*		bb.clear();
		bb.put ("Odgovor".getBytes());
		bb.flip();
		
		while (bb.hasRemaining()) {
			socketChannel.write(bb);
			}
*/		
//		ChannelIO cio = ChannelIO.getInstance(socketChannel, false);

		RequestServicerBuffer svc = new RequestServicerBuffer(socketChannel, mainDataStructure, buffer);
		xec.execute(svc);

/*		
		if (buffer.get(buffer.limit()-1) == 0)
		{
			//System.out.println("Zatvaranje - "+buffer.getShort(3));
			// Close channel on EOF; invalidates the key
			socketChannel.close();
			return;
		}
	
		if (count<0){
			socketChannel.close();
		}
*/
	}

}